﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace SampleImageGenerator.Rendering
{
	internal class LinearPalette : IPalette
	{
		public class ColourPoint : IComparable<ColourPoint>
		{
			public Double Value { get; set; }
			public Color Colour { get; set; }

			/// <summary>
			/// Gets or sets the colour as an ARGB value. This is required
			/// to allow the XML serialiser to serialise this object as 
			/// Color does not have a default constructor or settable
			/// properties.
			/// </summary>
			public Int32 ColourAsArgb
			{
				get
				{
					return Colour.ToArgb();
				}
				set
				{
					Colour = Color.FromArgb(value);
				}
			}

			public ColourPoint()
				: this(0, Color.Black)
			{
			}

			public ColourPoint(ColourPoint colourPoint)
				: this(colourPoint.Value, colourPoint.Colour)
			{
			}

			public ColourPoint(Double value, Color colour)
			{
				Value = value;
				Colour = colour;
			}

			public int CompareTo(ColourPoint other)
			{
				Int32 result = this.Value.CompareTo(other.Value);
				return result;
			}
		}

		private ColourPoint[] m_colourPoints;
		private Double m_minValue;
		private Double m_maxValue; 
		private Double m_valueScale;
		
		public String Name { get; set; }

		public LinearPalette()
		{
			ColourPoint[] colourPoints = new ColourPoint[] {
				new ColourPoint(-1, Color.Black),
				new ColourPoint(0, Color.Gray),
				new ColourPoint(1, Color.White)
			};

			ColourPoints = colourPoints;
			SetRange(-1, 1);
		}

		public ColourPoint[] ColourPoints
		{
			get
			{
				ColourPoint[] copy = CopyColourPoints(m_colourPoints);
				return copy;
			}

			set
			{
				ColourPoint[] copy = CopyColourPoints(value);
				Array.Sort<ColourPoint>(copy);
				m_colourPoints = copy;
				UpdateConstants();
			}
		}

		private static ColourPoint[] CopyColourPoints(ColourPoint[] original)
		{
			ColourPoint[] copy = new ColourPoint[original.Length];
			for (Int32 ii = 0; ii < original.Length; ii++)
			{
				copy[ii] = new ColourPoint(original[ii]);
			}
			return copy;
		}

		public Color GetColour(Double value)
		{
			Int32 count = m_colourPoints.Length;
			if (count < 2)
			{
				throw new InvalidOperationException("At least two colour points must be provided.");
			}

			Double minColourKey = m_colourPoints[0].Value;
			Double scaledValue = minColourKey + ((value - m_minValue) * m_valueScale);

			ColourPoint previousColourPoint = new ColourPoint();
			ColourPoint nextColourPoint = new ColourPoint();
			Int32 countMinusOne = count - 1;
			for (Int32 ii = 0; ii < countMinusOne; ii++)
			{
				previousColourPoint = m_colourPoints[ii];
				nextColourPoint = m_colourPoints[ii + 1];

				if (nextColourPoint.Value > scaledValue)
				{
					break;
				}
			}

			Color firstColour = previousColourPoint.Colour;
			Color lastColour = nextColourPoint.Colour;
			Double fraction = (nextColourPoint.Value - scaledValue) / (nextColourPoint.Value - previousColourPoint.Value); 
			Color result = BlendColours(firstColour, lastColour, fraction);
			return result; 
		}
	
		public void SetRange(Double minValue, Double maxValue)
		{
			m_minValue = minValue;
			m_maxValue = maxValue;
			UpdateConstants();
		}

		private Color BlendColours(Color firstColour, Color lastColour, double fraction)
		{
			Double antiFraction = 1 - fraction;
			Int32 alpha = (Int32)((firstColour.A * fraction) + (lastColour.A * (antiFraction)));
			Int32 red = (Int32)((firstColour.R * fraction) + (lastColour.R * (antiFraction)));
			Int32 green = (Int32)((firstColour.G * fraction) + (lastColour.G * (antiFraction)));
			Int32 blue = (Int32)((firstColour.B * fraction) + (lastColour.B * (antiFraction)));

			Color result = Color.FromArgb(alpha, red, green, blue);
			return result;
		}

		private Double ClampValue(Double value, Double min, Double max)
		{
			if (value < min) { return min; }
			if (value > max) { return max; }
			return value; 
		}

		private void UpdateConstants()
		{
			Double minColourKey = m_colourPoints[0].Value;
			Double maxColourKey = m_colourPoints[m_colourPoints.Length - 1].Value;
			m_valueScale = (maxColourKey - minColourKey) / (m_maxValue - m_minValue);
		}
	}
}
